package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"go_final/mxshop_srvs/order_srv/global"
	"go_final/mxshop_srvs/order_srv/model"
	goodspb "go_final/mxshop_srvs/order_srv/proto/protofile/goods"
	inventorypb "go_final/mxshop_srvs/order_srv/proto/protofile/inventory"
	orderpb "go_final/mxshop_srvs/order_srv/proto/protofile/order"
	"math/rand"
	"time"

	"github.com/opentracing/opentracing-go"

	"go.uber.org/zap"

	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/producer"

	"github.com/apache/rocketmq-client-go/v2/primitive"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"gorm.io/gorm"

	"google.golang.org/protobuf/types/known/emptypb"
)

type Server struct{}

// genOrderSn 生成订单编号
// 1.一般来说我们更倾向于生成数字类型的订单编号, 这样可以提高检索效率(比如时间戳)
// 2.如果有需要, 可以以时间日期开头, 这样的订单编号更叫具有可读性
func genOrderSn(userID int32) string {
	/*
		订单生成规则
		年月日时分秒 + 用户ID + 2位随机数
	*/
	now := time.Now()
	rand.Seed(time.Now().UnixNano()) // 设置随机因子
	orderSn := fmt.Sprintf("%s%d%d", now.Format("20060102150405"), userID, rand.Intn(90)+10)
	return orderSn
}

func (s Server) CartItemList(c context.Context, info *orderpb.UserInfo) (*orderpb.CartItemListResponse, error) {
	var shopCartItems []model.ShoppingCart
	res := global.DB.Where(model.ShoppingCart{
		UserID: info.UserID,
	}).Find(&shopCartItems)

	if res.Error != nil {
		return nil, res.Error
	}

	rsp := orderpb.CartItemListResponse{
		Total: int32(res.RowsAffected),
	}

	for _, shopCartItem := range shopCartItems {
		rsp.Data = append(rsp.Data, &orderpb.ShopCartItemInfo{
			ID:      shopCartItem.ID,
			UserID:  shopCartItem.UserID,
			GoodsID: shopCartItem.GoodsID,
			Num:     shopCartItem.Num,
			Checked: shopCartItem.Checked,
		})
	}

	return &rsp, nil
}

func (s Server) CreateCartItem(c context.Context, request *orderpb.CartItemRequest) (*orderpb.ShopCartItemInfo, error) {
	// TODO 为了严谨性, 需要先查询商品服务看商品在不在, 否则下单的时候会报错
	var cartItem model.ShoppingCart
	res := global.DB.Where(&model.ShoppingCart{
		UserID:  request.UserID,
		GoodsID: request.GoodsID,
	}).First(&cartItem)

	if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
		return nil, res.Error
	}

	if res.RowsAffected == 0 {
		cartItem.Num = request.Num
		cartItem.UserID = request.UserID
		cartItem.GoodsID = request.GoodsID
		cartItem.Checked = request.Checked
	} else {
		cartItem.Num += request.Num
	}

	res = global.DB.Save(&cartItem)
	if res.Error != nil {
		return nil, res.Error
	}

	return &orderpb.ShopCartItemInfo{
		ID: cartItem.ID,
	}, nil
}

func (s Server) UpdateCartItem(c context.Context, request *orderpb.CartItemRequest) (*emptypb.Empty, error) {
	var shopCart model.ShoppingCart
	res := global.DB.Where(&model.ShoppingCart{
		GoodsID: request.GoodsID,
		UserID:  request.UserID,
	}).First(&shopCart)
	if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
		return nil, res.Error
	}
	if res.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "没有该购物车记录")
	}

	shopCart.Checked = request.Checked
	if request.Num > 0 {
		shopCart.Num = request.Num
	}

	res = global.DB.Save(&shopCart)
	if res.Error != nil {
		return nil, res.Error
	}

	return &emptypb.Empty{}, nil
}

func (s Server) DeleteCartItem(c context.Context, request *orderpb.CartItemRequest) (*emptypb.Empty, error) {
	if request.GoodsID == 0 {
		return nil, status.Error(codes.NotFound, "未选中要删除的商品")
	}

	res := global.DB.Where(&model.ShoppingCart{
		GoodsID: request.GoodsID,
		UserID:  request.UserID,
	}).Delete(&model.ShoppingCart{})
	if res.Error != nil {
		return nil, res.Error
	}

	if res.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "购物车记录不存在, 删除购物车记录失败")
	}

	return &emptypb.Empty{}, nil
}

type OrderListener struct {
	Code        codes.Code
	Detail      string
	ID          int32
	OrderAmount float32
	Ctx         context.Context
}

func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	parentSpan := opentracing.SpanFromContext(o.Ctx)

	// 1.从购物车中获取到选中的商品
	var shopCarts []model.ShoppingCart
	shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopCart", opentracing.ChildOf(parentSpan.Context()))
	res := global.DB.Where(model.ShoppingCart{
		UserID:  orderInfo.UserID,
		Checked: true,
	}).Find(&shopCarts)
	shopCartSpan.Finish()

	if res.Error != nil {
		o.Code = codes.InvalidArgument
		o.Detail = res.Error.Error()
		// 还没有创建订单, 回滚消息
		return primitive.RollbackMessageState
	}

	if res.RowsAffected == 0 {
		o.Code = codes.InvalidArgument
		o.Detail = "未选中结算的商品"
		// 还没有创建订单, 回滚消息
		return primitive.RollbackMessageState
	}

	// 2.计算商品总金额 - 访问商品服务
	var goodsIDs []int32
	var goodsCount = make(map[int32]int32) // 每个商品id对应的商品数量
	for _, goods := range shopCarts {
		goodsCount[goods.GoodsID] = goods.Num
		goodsIDs = append(goodsIDs, goods.GoodsID)
	}

	queryGoodsSpan := opentracing.GlobalTracer().StartSpan("query_goods", opentracing.ChildOf(parentSpan.Context()))
	goodsInfos, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &goodspb.BatchGoodsIdInfo{
		Id: goodsIDs,
	})
	queryGoodsSpan.Finish()
	if err != nil {
		o.Code = codes.Internal
		o.Detail = "批量查询商品信息失败"
		return primitive.RollbackMessageState
	}

	var totalAmount float32            // 订单总金额
	var orderGoods []*model.OrderGoods // 用于插入订单表
	goodsInvInfo := inventorypb.SellInfo{
		OrderSn: orderInfo.OrderSn,
	} // 用于扣减库存
	for _, goodsInfo := range goodsInfos.Data {
		totalAmount += goodsInfo.ShopPrice * float32(goodsCount[goodsInfo.Id])
		orderGoods = append(orderGoods, &model.OrderGoods{
			//OrderID:    0,	// 目前没有, 订单表生成好了以后再填入
			GoodsID:    goodsInfo.Id,
			GoodsName:  goodsInfo.Name,
			GoodsImage: goodsInfo.GoodsFrontImage,
			GoodsPrice: goodsInfo.ShopPrice,
			Num:        goodsCount[goodsInfo.Id],
		})
		goodsInvInfo.GoodsInfo = append(goodsInvInfo.GoodsInfo, &inventorypb.GoodsInvInfo{
			GoodsID: goodsInfo.Id,
			Num:     goodsCount[goodsInfo.Id],
		})
	}

	// 3.扣减库存 - 访问库存服务
	invokeSellSpan := opentracing.GlobalTracer().StartSpan("invoke_inv_sell", opentracing.ChildOf(parentSpan.Context()))
	_, err = global.InvSrvClient.Sell(context.Background(), &goodsInvInfo)
	if err != nil {
		// 判断是不是网络问题引起的err, 如果是真的通过err的code来判断, 如果真是是扣减库存失败, 再去RollbackMessageState, 否则不操作, 继续往下执行
		fmt.Println("err.Error()", err)
		o.Code = codes.Internal
		o.Detail = "库存扣减失败"
		return primitive.RollbackMessageState
	}
	invokeSellSpan.Finish()

	// 开启本地事务
	tx := global.DB.Begin()

	// 4.生成订单的基本信息表
	orderInfo.OrderAmount = totalAmount
	saveOrderSpan := opentracing.GlobalTracer().StartSpan("save_orderInfo", opentracing.ChildOf(parentSpan.Context()))
	res = tx.Create(&orderInfo)
	saveOrderSpan.Finish()

	if res.Error != nil {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = res.Error.Error()
		// 订单创建失败了, 需要提交回滚的消息
		return primitive.CommitMessageState
	}
	if res.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "生成订单失败"
		// 订单创建失败了, 需要提交回滚的消息
		return primitive.CommitMessageState
	}

	o.OrderAmount = totalAmount
	o.ID = orderInfo.ID
	for _, goods := range orderGoods {
		goods.OrderID = orderInfo.ID
	}

	// 5.插入到订单的商品信息表
	saveOrderGoodsSpan := opentracing.GlobalTracer().StartSpan("save_order_goods", opentracing.ChildOf(parentSpan.Context()))
	res = tx.CreateInBatches(&orderGoods, 100)
	saveOrderGoodsSpan.Finish()
	if res.Error != nil {
		tx.Rollback()
		// 如果出错, 要归还库存
		o.Code = codes.Internal
		o.Detail = res.Error.Error()
		return primitive.CommitMessageState
	}
	if res.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "生成订单商品失败"
		return primitive.CommitMessageState
	}

	// 6.从购物车表中删除选中的商品
	deleteShopCartsSpan := opentracing.GlobalTracer().StartSpan("delete_shop_carts", opentracing.ChildOf(parentSpan.Context()))
	res = tx.Where(model.ShoppingCart{
		UserID:  orderInfo.UserID,
		Checked: true,
	}).Delete(&model.ShoppingCart{})
	deleteShopCartsSpan.Finish()

	if res.Error != nil {
		// 如果出错, 要归还库存
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = res.Error.Error()
		return primitive.CommitMessageState
	}
	if res.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "购物车表中删除选中的商品失败"
		return primitive.CommitMessageState
	}

	// 发送延时消息实现订单超时自动取消订单
	p, _ := rocketmq.NewProducer(producer.WithNameServer([]string{"120.24.67.46:9876"}))
	if err := p.Start(); err != nil {
		tx.Rollback()
		zap.S().Errorf("生成producer失败: %v \n", err)
		o.Code = codes.Internal
		o.Detail = "生成producer失败"
		return primitive.CommitMessageState
	}
	topic := "order_timeout"

	msg = &primitive.Message{
		Topic: topic,
		Body:  msg.Body,
	}
	msg.WithDelayTimeLevel(5) // 16是30分钟
	_, err = p.SendSync(context.Background(), msg)
	if err != nil {
		tx.Rollback()
		zap.S().Errorf("发送延时消息失败: %v \n", err)
		o.Code = codes.Internal
		o.Detail = "发送延时消息失败"
		return primitive.CommitMessageState
	}

	tx.Commit()
	o.Code = codes.OK

	return primitive.RollbackMessageState
}

func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	// 检查之前的逻辑是否完成, 通过orderSn查询订单表是否正确写入

	res := global.DB.Where(model.OrderInfo{
		OrderSn: orderInfo.OrderSn,
	}).First(&orderInfo)
	if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
		// 没有查询到, 但是库存不一定已经扣减了, 因为在执行事务的时候如果出问题, 那么就不需要归还库存, 所以这里要做幂等性校验
		return primitive.CommitMessageState
	}
	if res.RowsAffected == 0 {
		return primitive.CommitMessageState
	}

	return primitive.RollbackMessageState
}

// Create 从购物车中创建订单
func (s Server) Create(c context.Context, request *orderpb.OrderRequest) (*orderpb.OrderInfoResponse, error) {
	/**
	1.从购物车中获取到选中的商品
	2.计算商品总金额 - 访问商品服务
	3.扣减库存 - 访问库存服务
	4.订单的基本信息表, 订单的商品信息表
	5.从购物车中删除到下单的商品
	*/
	orderListener := &OrderListener{
		Ctx: c,
	}
	p, err := rocketmq.NewTransactionProducer(
		orderListener,
		producer.WithNameServer([]string{"120.24.67.46:9876"}),
	)
	if err != nil {
		zap.S().Error("生成mq producer失败: %s", err.Error())
		return nil, err
	}
	err = p.Start()
	if err != nil {
		fmt.Printf("启动mq producer失败: %s", err.Error())
		return nil, err
	}

	orderInfo := model.OrderInfo{
		OrderSn:      genOrderSn(request.UserID),
		Address:      request.Address,
		SignerName:   request.Name,
		SignerMobile: request.Mobile,
		Post:         request.Post,
		UserID:       request.UserID,
	}

	jsonStringOrderInfo, _ := json.Marshal(orderInfo)

	sendTransactionMessageRes, err := p.SendMessageInTransaction(
		context.Background(),
		primitive.NewMessage(
			"order_reback",
			jsonStringOrderInfo,
		),
	)
	if err != nil {
		fmt.Printf("发送失败 : %s\n", err)
		return nil, status.Error(codes.Internal, "发送mq消息失败")
	} else {
		fmt.Printf("发送成功: result=%s\n", sendTransactionMessageRes.String())
	}

	if orderListener.Code != codes.OK {
		// 本地事务执行错误了, 需要归还库存了
		return nil, status.Error(orderListener.Code, orderListener.Detail)
	}

	// TODO 如果操作失败, 需要归还库存(分布式事务)
	// 本地事务执行完以后orderListener上面会有订单ID和订单金额
	return &orderpb.OrderInfoResponse{
		ID:      orderListener.ID,
		OrderSn: orderInfo.OrderSn,
		Total:   orderListener.OrderAmount,
	}, nil
}

// OrderList 查询订单列表
func (s Server) OrderList(c context.Context, request *orderpb.OrderFilterRequest) (*orderpb.OrderListResponse, error) {
	var orders []model.OrderInfo

	res := global.DB.Scopes(Paginate(int(request.Pages), int(request.PagesPerNum))).Where(&model.OrderInfo{
		UserID: request.UserID,
	}).Find(&orders)

	if res.Error != nil {
		return nil, res.Error
	}

	// 查询总条数
	var count int64
	res = global.DB.Model(&model.OrderInfo{}).Where(&model.OrderInfo{
		UserID: request.UserID,
	}).Count(&count)

	if res.Error != nil {
		return nil, res.Error
	}

	rsp := orderpb.OrderListResponse{
		Total: int32(count),
	}

	for _, order := range orders {
		rsp.Data = append(rsp.Data, &orderpb.OrderInfoResponse{
			ID:      order.ID,
			UserID:  order.UserID,
			OrderSn: order.OrderSn,
			PayType: order.PayType,
			Status:  order.Status,
			Post:    order.Post,
			Total:   order.OrderAmount,
			Address: order.Address,
			Name:    order.SignerName,
			Mobile:  order.SignerMobile,
		})
	}

	return &rsp, nil
}

// OrderDetail 查询订单详情
func (s Server) OrderDetail(c context.Context, request *orderpb.OrderRequest) (*orderpb.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	// 如果是管理员, 就不传UserID, 如果是普通用户, 就传UserID过来
	res := global.DB.Where(model.OrderInfo{
		UserID: request.UserID,
	}).First(&order, request.ID)

	if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
		return nil, res.Error
	}

	if res.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "订单不存在")
	}

	rsp := orderpb.OrderInfoDetailResponse{
		OrderInfo: &orderpb.OrderInfoResponse{
			ID:      order.ID,
			UserID:  order.UserID,
			OrderSn: order.OrderSn,
			PayType: order.PayType,
			Status:  order.Status,
			Post:    order.Post,
			Total:   order.OrderAmount,
			Address: order.Address,
			Name:    order.SignerName,
			Mobile:  order.SignerMobile,
		},
	}

	var orderGoods []model.OrderGoods
	res = global.DB.Where(model.OrderGoods{
		OrderID: order.ID,
	}).Find(&orderGoods)

	if res.Error != nil {
		return nil, res.Error
	}

	if res.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "订单商品为空")
	}

	for _, goods := range orderGoods {
		rsp.Goods = append(rsp.Goods, &orderpb.OrderItemResponse{
			ID:         goods.ID,
			OrderID:    goods.OrderID,
			GoodsID:    goods.GoodsID,
			GoodsName:  goods.GoodsName,
			GoodsImage: goods.GoodsImage,
			GoodsPrice: goods.GoodsPrice,
			Num:        goods.Num,
		})
	}

	return &rsp, nil
}

func (s Server) UpdateOrderStatus(c context.Context, request *orderpb.OrderStatus) (*emptypb.Empty, error) {
	fmt.Println("request", request)
	fmt.Println("request.OrderSn", request.OrderSn)
	res := global.DB.Model(model.OrderInfo{}).Where(model.OrderInfo{
		OrderSn: request.OrderSn,
	}).Update("status", request.Status)

	if res.Error != nil {
		return nil, res.Error
	}

	if res.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "更新失败")
	}

	return &emptypb.Empty{}, nil
}
